package world.snowcrystal.service;

import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import world.snowcrystal.dao.UserAuthenticationDetails;
import world.snowcrystal.dto.RegistrationRequestMessage;
import world.snowcrystal.exception.*;

import java.sql.SQLException;

public interface UserAuthenticationService  {


    /**
     * 将用户添加到数据库之前，将对密码进行加密，加密操作不对传入对象的 password 字段造成影响；另外，在插入时，
     * 需要将数据库生成的主键回插到实例上.
     * KG 表示 <a href="https://mybatis.org/mybatis-3/sqlmap-xml.html#insert-update-and-delete">Key Generation</a>
     * <p>这是为新用户插入所使用的简单插入方法，对象应只包含如下几个字段</p>
     * <li>username</li>
     * <li>password</li>
     * <li>email</li>
     * <li>userRegisterIp</li>
     * @return int 返回插入成功的条目数
     * @since 1.0
     * @author tianqing
     */
    int addNewUserKG(UserAuthenticationDetails userAuthenticationDetails)
            throws SQLException;

    /**
     * 用于更新用户的认证信息
     * @param userAuthenticationDetails 如果提供的字段不为 null ，那么字段将被更新到数据库中
     * @since 1.0
     * @author Tianqing
     */
    void updateUserAuthenticationDetails(UserAuthenticationDetails userAuthenticationDetails);

    /**
     * 根据 username 进行密码更新，显然由于 username 具有唯一约束，这将仅仅影响一行
     *
     * @param username 用户名
     * @param oldPassword 业务可根据需求提供 oldPassword ，当提供时，将对这个字段进行检查是否与原密码匹配
     * @param newPassword 新密码
     * @throws SnowcrystalUserNotFoundException 当用户 username 不存在时抛出该异常
     * @throws SnowcrystalPasswordMismatchException 当 oldPassword 与数据库中密码不匹配时抛出该异常
     * @throws SnowcrystalOperationFailedException 当受影响的行数不为 1 时抛出该异常
     * @since 1.0
     * @author tianqing
     */
    void updateUserPassword(String username , @Nullable String oldPassword, String newPassword) throws SnowcrystalUserNotFoundException, SnowcrystalPasswordMismatchException, SnowcrystalOperationFailedException;


    /**
     * 根据 id 进行密码更新，显然，这将仅仅影响一行
     *
     * @param id 用户 id
     * @param oldPassword 业务可根据需求提供 oldPassword ，当提供时，将对这个字段进行检查是否与原密码匹配
     * @param newPassword 新密码
     * @throws SnowcrystalUserNotFoundException 当用户 id 不存在时抛出该异常
     * @throws SnowcrystalPasswordMismatchException 当 oldPassword 与数据库中密码不匹配时抛出该异常
     * @throws SnowcrystalOperationFailedException 当受影响的行数不为 1 时抛出该异常
     * @since 1.0
     * @author tianqing
     */
    void updateUserPassword(Long id , @Nullable String oldPassword, String newPassword) throws SnowcrystalUserNotFoundException, SnowcrystalPasswordMismatchException, SnowcrystalOperationFailedException;


    /**
     * 授予用户权限
     * @param authorities 以字符串表示的权限信息，详见 {@link UserAuthenticationDetails}
     * @since 1.0
     * @author Tianqing
     */

    void grantUserAuthorities(String ...authorities);

    /**
     * 收回用户权限信息，如果用户没有某个权限，则对于这个权限什么都不做
     * @param authorities 将要被收回的权限
     * @see #grantUserAuthorities(String...)
     * @since 1.0
     * @author Tianqing
     */
    void revokeUserAuthorities(String ...authorities);



    boolean authenticateUser(String username, String password);


    /**
     *将回执码信息保存在队列中，要求信息具有时效性，
     * 当信息过期时，需要从队列中驱除
     * 如果对应的信息已经存在，则进行替换，并刷新 ttl
     * @since 1.0
     * @author tianqing
     */
    void putRegistrationRequest(RegistrationRequestMessage msg);


    /**
     *检查回执码是否有效
     * @param mail 用于注册所使用的邮箱
     * @return <code>true</code> 如果在队列中仍然可以通过 mail 找到对应的回执码，并且匹配；除此之外，返回<code>false</code>
     * @throws SnowcrystalValidationCodeMismatchException 如果传入的回执码与队列中的回执码不匹配，则抛出该异常
     * @throws SnowcrystalValidationCodeExpireException 如果未能通过 mail 找到对应的回执码，则抛出该异常
     * @since 1.0
     * @author tianqing
     */
    boolean checkValidationCode(String mail,String verificationCode)
            throws SnowcrystalValidationCodeExpireException, SnowcrystalValidationCodeMismatchException;

    /**
     * 生成具有固定长度的认证码
     * @since 1.0
     * @author tianqing
     */
    String generateVerificationCode();


    /**
     * 生成 in-place 操作的 token，要求具有一定安全性
     * 以 12-16 个字符为宜，并将其存储到队列中暂时保存
     * @since 1.0
     * @author tianqing
     */
    String generateInPlaceOperationToken();

    /**
     * 检查 in-place 操作 token 是否已经过期，如果已经过期，则返回 false
     * @since 1.0
     * @author tianqing
     */
    boolean checkInPlaceOperationTokenAlive(@Nonnull String verificationCode);


    /**
     * 生成并设置 in-place token
     * <li> 生成 token</li>
     * <li> 将其保存到队列</li>
     * <li> 设置 cookie</li>
     *
     * @since 1.0
     * @author tianqing
     * @return token 返回生成的 token
     */
    String setInPlaceOperationToken(HttpServletRequest request,
                                  HttpServletResponse response);




}
